// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <fbl/string_buffer.h>
#include <zircon/assert.h>

#include <lib/syslog/logger.h>
#include <lib/syslog/wire_format.h>

#include <atomic>
#include <stdio.h>

#include "fx_logger.h"

namespace {

// This thread's koid.
// Initialized on first use.
thread_local zx_koid_t tls_thread_koid{ZX_KOID_INVALID};

zx_koid_t GetCurrentThreadKoid() {
  if (unlikely(tls_thread_koid == ZX_KOID_INVALID)) {
    tls_thread_koid = GetKoid(zx_thread_self());
  }
  ZX_DEBUG_ASSERT(tls_thread_koid != ZX_KOID_INVALID);
  return tls_thread_koid;
}

}  // namespace

void fx_logger::ActivateFallback(int fallback_fd) {
  fbl::AutoLock lock(&fallback_mutex_);
  if (logger_fd_.load(std::memory_order_relaxed) != -1) {
    return;
  }
  ZX_DEBUG_ASSERT(fallback_fd >= -1);
  if (tagstr_.empty()) {
    for (size_t i = 0; i < tags_.size(); i++) {
      if (tagstr_.empty()) {
        tagstr_ = tags_[i];
      } else {
        tagstr_ = fbl::String::Concat({tagstr_, ", ", tags_[i]});
      }
    }
  }
  if (fallback_fd == -1) {
    fallback_fd = STDERR_FILENO;
  }
  // Do not change fd_to_close_ as we don't want to close fallback_fd.
  // We will still close original console_fd_
  logger_fd_.store(fallback_fd, std::memory_order_relaxed);
}

zx_status_t fx_logger::VLogWriteToSocket(fx_log_severity_t severity, const char* tag,
                                         const char* msg, va_list args, bool perform_format) {
  zx_time_t time = zx_clock_get_monotonic();
  fx_log_packet_t packet;
  memset(&packet, 0, sizeof(packet));
  constexpr size_t kDataSize = sizeof(packet.data);
  packet.metadata.pid = pid_;
  packet.metadata.tid = GetCurrentThreadKoid();
  packet.metadata.time = time;
  packet.metadata.severity = severity;
  packet.metadata.dropped_logs = dropped_logs_.load();

  // Write tags
  size_t pos = 0;
  for (size_t i = 0; i < tags_.size(); i++) {
    size_t len = tags_[i].length();
    ZX_DEBUG_ASSERT(len < 128);
    packet.data[pos++] = static_cast<char>(len);
    memcpy(packet.data + pos, tags_[i].c_str(), len);
    pos += len;
  }
  if (tag != NULL) {
    size_t len = strlen(tag);
    if (len > 0) {
      size_t write_len = fbl::min(len, static_cast<size_t>(FX_LOG_MAX_TAG_LEN - 1));
      ZX_DEBUG_ASSERT(write_len < 128);
      packet.data[pos++] = static_cast<char>(write_len);
      memcpy(packet.data + pos, tag, write_len);
      pos += write_len;
    }
  }
  packet.data[pos++] = 0;
  ZX_DEBUG_ASSERT(pos < kDataSize);
  // Write msg
  int n = static_cast<int>(kDataSize - pos);
  int count = 0;
  size_t msg_pos = pos;
  if (!perform_format) {
    size_t write_len = fbl::min(strlen(msg), static_cast<size_t>(n - 1));
    memcpy(packet.data + pos, msg, write_len);
    pos += write_len;
    packet.data[pos] = 0;
    count = static_cast<int>(write_len + 1);
  } else {
    count = vsnprintf(packet.data + pos, n, msg, args);
    if (count < 0) {
      return ZX_ERR_INVALID_ARGS;
    }
  }
  if (count >= n) {
    // truncated
    constexpr char kEllipsis[] = "...";
    constexpr size_t kEllipsisSize = sizeof(kEllipsis) - 1;
    memcpy(packet.data + kDataSize - 1 - kEllipsisSize, kEllipsis, kEllipsisSize);
    count = n - 1;
  }
  auto size = sizeof(packet.metadata) + msg_pos + count + 1;
  ZX_DEBUG_ASSERT(size <= sizeof(packet));
  auto status = socket_.write(0, &packet, size, nullptr);
  if (status == ZX_ERR_BAD_STATE || status == ZX_ERR_PEER_CLOSED) {
    ActivateFallback(-1);
    return VLogWriteToFd(logger_fd_.load(std::memory_order_relaxed), severity, tag,
                         packet.data + msg_pos, args, false);
  }
  if (status != ZX_OK) {
    dropped_logs_.fetch_add(1);
  }
  return status;
}

zx_status_t fx_logger::VLogWriteToFd(int fd, fx_log_severity_t severity, const char* tag,
                                     const char* msg, va_list args, bool perform_format) {
  zx_time_t time = zx_clock_get_monotonic();
  constexpr char kEllipsis[] = "...";
  constexpr size_t kEllipsisSize = sizeof(kEllipsis) - 1;
  constexpr size_t kMaxMessageSize = 2043;

  fbl::StringBuffer<kMaxMessageSize + kEllipsisSize + 1 /*\n*/> buf;
  buf.AppendPrintf("[%05ld.%06ld]", time / 1000000000UL, (time / 1000UL) % 1000000UL);
  buf.AppendPrintf("[%ld]", pid_);
  buf.AppendPrintf("[%ld]", GetCurrentThreadKoid());

  buf.Append("[");
  if (!tagstr_.empty()) {
    buf.Append(tagstr_);
  }

  if (tag != NULL) {
    size_t len = strlen(tag);
    if (len > 0) {
      if (!tagstr_.empty()) {
        buf.Append(", ");
      }
      buf.Append(tag, fbl::min(len, static_cast<size_t>(FX_LOG_MAX_TAG_LEN - 1)));
    }
  }
  buf.Append("]");
  switch (severity) {
    case FX_LOG_INFO:
      buf.Append(" INFO");
      break;
    case FX_LOG_WARNING:
      buf.Append(" WARNING");
      break;
    case FX_LOG_ERROR:
      buf.Append(" ERROR");
      break;
    case FX_LOG_FATAL:
      buf.Append(" FATAL");
      break;
    default:
      buf.AppendPrintf(" VLOG(%d)", -severity);
  }
  buf.Append(": ");

  if (!perform_format) {
    buf.Append(msg);
  } else {
    buf.AppendVPrintf(msg, args);
  }
  if (buf.size() > kMaxMessageSize) {
    buf.Resize(kMaxMessageSize);
    buf.Append(kEllipsis);
  }
  buf.Append('\n');
  ssize_t status = write(fd, buf.data(), buf.size());
  if (status < 0) {
    return ZX_ERR_IO;
  }
  return ZX_OK;
}

zx_status_t fx_logger::VLogWrite(fx_log_severity_t severity, const char* tag, const char* msg,
                                 va_list args, bool perform_format) {
  if (msg == NULL || severity > FX_LOG_FATAL) {
    return ZX_ERR_INVALID_ARGS;
  }
  if (GetSeverity() > severity) {
    return ZX_OK;
  }

  zx_status_t status;
  int fd = logger_fd_.load(std::memory_order_relaxed);
  if (fd != -1) {
    status = VLogWriteToFd(fd, severity, tag, msg, args, perform_format);
  } else if (socket_.is_valid()) {
    status = VLogWriteToSocket(severity, tag, msg, args, perform_format);
  } else {
    return ZX_ERR_BAD_STATE;
  }
  if (severity == FX_LOG_FATAL) {
    abort();
  }
  return status;
}

// This function is not thread safe
zx_status_t fx_logger::AddTags(const char** tags, size_t ntags) {
  if (ntags > FX_LOG_MAX_TAGS) {
    return ZX_ERR_INVALID_ARGS;
  }

  auto fd_mode = logger_fd_.load(std::memory_order_relaxed) != -1;
  for (size_t i = 0; i < ntags; i++) {
    auto len = strlen(tags[i]);
    fbl::String str(tags[i], len > FX_LOG_MAX_TAG_LEN - 1 ? FX_LOG_MAX_TAG_LEN - 1 : len);
    if (fd_mode) {
      if (tagstr_.empty()) {
        tagstr_ = str;
      } else {
        tagstr_ = fbl::String::Concat({tagstr_, ", ", str});
      }
    } else {
      tags_.push_back(str);
    }
  }
  return ZX_OK;
}
